﻿using System;
using System.Drawing;
using System.Windows.Forms;

namespace FogBugzCaseTracker
{
    public interface ITrackingForm
    {
        Rectangle Bounds { get; set; }
        event EventHandler Load;
        event FormClosedEventHandler FormClosed;
        void CursorEnter();
        void CursorLeave();
    }

    /// <remarks>
    /// Inspired by (and mostly copied from)
    /// <see href="http://netcode.ru/dotnet/?lang=&katID=30&skatID=283&artID=7862"/>.
    /// </remarks>
    public class CursorTracker : IMessageFilter
    {
        private const int WM_NCMOUSEMOVE = 0x00A0;
        private const int WM_MOUSEMOVE = 0x200;
        private const int WM_NCMOUSELEAVE = 0x02A2;
        private const int WM_MOUSELEAVE = 0x02A3;

		private bool _cursorInBounds;
        private readonly Timer _boundsChecker = new Timer();
        private readonly ITrackingForm _parentForm;

        public CursorTracker(ITrackingForm parentForm)
		{
            _boundsChecker.Tick += _boundsChecker_Tick;
			_parentForm = parentForm;
            _parentForm.Load += _parentForm_Load;
            _parentForm.FormClosed += _parentForm_FormClosed;
		}

        void _boundsChecker_Tick(object sender, EventArgs e)
        {
            _boundsChecker.Stop();
            UpdateCursorBounds();
            _boundsChecker.Start();
        }

        void _parentForm_Load(object sender, EventArgs e)
        {
            Application.AddMessageFilter(this);
            _boundsChecker.Interval = 1000;
            _boundsChecker.Start();
        }

        void _parentForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            _boundsChecker.Stop();
            Application.RemoveMessageFilter(this);
        }

		public bool PreFilterMessage(ref Message m)
		{
			switch (m.Msg)
			{
				case WM_MOUSEMOVE:
				case WM_NCMOUSEMOVE:
					CheckCursorBounds(true);
					break;

				case WM_NCMOUSELEAVE:
				case WM_MOUSELEAVE:
					CheckCursorBounds(false);
					break;
			}
   
			return false; // don't actually filter the message
		}

		private void CheckCursorBounds(bool cursorMove)
		{
			// Already know the cursor is in the bounds, so we don't
            // care that the cursor just moved (saves unnecessary checks
			// on the form bounds and raising events)
			if (_cursorInBounds && cursorMove)
				return;
            
			UpdateCursorBounds();
		}

		private void UpdateCursorBounds()
		{
            // if the cursor is in the bounds of the current form 
            // set the bounds to true, else set the bounds to false
            var cursorInBounds = _parentForm.Bounds.Contains(Cursor.Position);

			// prevent setting the bounds status to the same as it was
			// already set
			if (cursorInBounds != _cursorInBounds)
			{
				_cursorInBounds = cursorInBounds;
			    // TODO: Should we try/catch around the callbacks?
				if (_cursorInBounds)
				{
                    _parentForm.CursorEnter();
				}
				else
				{
				    _parentForm.CursorLeave();
				}
			}
		}
    }
}
